

<!DOCTYPE html>
<html class="writer-html5" lang="en" >
<head>
  <meta charset="utf-8" />
  
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  
  <title>体系结构 &mdash; Ceph Documentation</title>
  

  
  <link rel="stylesheet" href="../_static/ceph.css" type="text/css" />
  <link rel="stylesheet" href="../_static/pygments.css" type="text/css" />
  <link rel="stylesheet" href="../_static/graphviz.css" type="text/css" />
  <link rel="stylesheet" href="../_static/css/custom.css" type="text/css" />

  
  
    <link rel="shortcut icon" href="../_static/favicon.ico"/>
  

  
  

  

  
  <!--[if lt IE 9]>
    <script src="../_static/js/html5shiv.min.js"></script>
  <![endif]-->
  
    
      <script type="text/javascript" id="documentation_options" data-url_root="../" src="../_static/documentation_options.js"></script>
        <script src="../_static/jquery.js"></script>
        <script src="../_static/underscore.js"></script>
        <script src="../_static/doctools.js"></script>
    
    <script type="text/javascript" src="../_static/js/theme.js"></script>

    
    <link rel="index" title="Index" href="../genindex/" />
    <link rel="search" title="Search" href="../search/" />
    <link rel="next" title="向 Ceph 贡献：开发者指南" href="../dev/developer_guide/" />
    <link rel="prev" title="cephadm" href="../api/mon_command_api/" /> 
</head>

<body class="wy-body-for-nav">

   
  <header class="top-bar">
    

















<div role="navigation" aria-label="breadcrumbs navigation">

  <ul class="wy-breadcrumbs">
    
      <li><a href="../" class="icon icon-home"></a> &raquo;</li>
        
      <li>体系结构</li>
    
    
      <li class="wy-breadcrumbs-aside">
        
          
            <a href="../_sources/architecture.rst.txt" rel="nofollow"> View page source</a>
          
        
      </li>
    
  </ul>

  
  <hr/>
</div>
  </header>
  <div class="wy-grid-for-nav">
    
    <nav data-toggle="wy-nav-shift" class="wy-nav-side">
      <div class="wy-side-scroll">
        <div class="wy-side-nav-search"  style="background: #eee" >
          

          
            <a href="../">
          

          
            
            <img src="../_static/logo.png" class="logo" alt="Logo"/>
          
          </a>

          

          
<div role="search">
  <form id="rtd-search-form" class="wy-form" action="../search/" method="get">
    <input type="text" name="q" placeholder="Search docs" />
    <input type="hidden" name="check_keywords" value="yes" />
    <input type="hidden" name="area" value="default" />
  </form>
</div>

          
        </div>

        
        <div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="main navigation">
          
            
            
              
            
            
              <ul class="current">
<li class="toctree-l1"><a class="reference internal" href="../start/intro/">Ceph 简介</a></li>
<li class="toctree-l1"><a class="reference internal" href="../install/">安装 Ceph</a></li>
<li class="toctree-l1"><a class="reference internal" href="../cephadm/">Cephadm</a></li>
<li class="toctree-l1"><a class="reference internal" href="../rados/">Ceph 存储集群</a></li>
<li class="toctree-l1"><a class="reference internal" href="../cephfs/">Ceph 文件系统</a></li>
<li class="toctree-l1"><a class="reference internal" href="../rbd/">Ceph 块设备</a></li>
<li class="toctree-l1"><a class="reference internal" href="../radosgw/">Ceph 对象网关</a></li>
<li class="toctree-l1"><a class="reference internal" href="../mgr/">Ceph 管理器守护进程</a></li>
<li class="toctree-l1"><a class="reference internal" href="../mgr/dashboard/">Ceph 仪表盘</a></li>
<li class="toctree-l1"><a class="reference internal" href="../api/">API 文档</a></li>
<li class="toctree-l1 current"><a class="current reference internal" href="#">体系结构</a><ul>
<li class="toctree-l2"><a class="reference internal" href="#ceph">Ceph 存储集群</a><ul>
<li class="toctree-l3"><a class="reference internal" href="#id2">数据的存储</a></li>
<li class="toctree-l3"><a class="reference internal" href="#index-0">伸缩性和高可用性</a><ul>
<li class="toctree-l4"><a class="reference internal" href="#crush">CRUSH 简介</a></li>
<li class="toctree-l4"><a class="reference internal" href="#index-2">集群运行图</a></li>
<li class="toctree-l4"><a class="reference internal" href="#index-3">高可用监视器</a></li>
<li class="toctree-l4"><a class="reference internal" href="#index-4">高可用性认证</a></li>
<li class="toctree-l4"><a class="reference internal" href="#index-5">智能程序支撑超大规模</a></li>
</ul>
</li>
<li class="toctree-l3"><a class="reference internal" href="#id8">动态集群管理</a><ul>
<li class="toctree-l4"><a class="reference internal" href="#index-6">关于存储池</a></li>
<li class="toctree-l4"><a class="reference internal" href="#pg-osd">PG 映射到 OSD</a></li>
<li class="toctree-l4"><a class="reference internal" href="#pg-id">计算 PG ID</a></li>
<li class="toctree-l4"><a class="reference internal" href="#index-9">互联和子集</a></li>
<li class="toctree-l4"><a class="reference internal" href="#index-10">重均衡</a></li>
<li class="toctree-l4"><a class="reference internal" href="#index-11">数据一致性</a></li>
</ul>
</li>
<li class="toctree-l3"><a class="reference internal" href="#index-12">纠删编码</a><ul>
<li class="toctree-l4"><a class="reference internal" href="#id14">读出和写入编码块</a></li>
<li class="toctree-l4"><a class="reference internal" href="#id15">被中断的完全重写</a></li>
</ul>
</li>
<li class="toctree-l3"><a class="reference internal" href="#id16">缓存分级</a></li>
<li class="toctree-l3"><a class="reference internal" href="#index-13">扩展 Ceph</a></li>
<li class="toctree-l3"><a class="reference internal" href="#id18">小结</a></li>
</ul>
</li>
<li class="toctree-l2"><a class="reference internal" href="#index-14">Ceph 协议</a><ul>
<li class="toctree-l3"><a class="reference internal" href="#librados">原生协议和 <code class="docutils literal notranslate"><span class="pre">librados</span></code></a></li>
<li class="toctree-l3"><a class="reference internal" href="#index-15">对象关注/通知</a></li>
<li class="toctree-l3"><a class="reference internal" href="#index-16">数据条带化</a></li>
</ul>
</li>
<li class="toctree-l2"><a class="reference internal" href="#index-17">Ceph 客户端</a><ul>
<li class="toctree-l3"><a class="reference internal" href="#index-18">Ceph 对象存储</a></li>
<li class="toctree-l3"><a class="reference internal" href="#index-19">Ceph 块设备</a></li>
<li class="toctree-l3"><a class="reference internal" href="#arch-cephfs">Ceph 文件系统</a></li>
</ul>
</li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="../dev/developer_guide/">开发者指南</a></li>
<li class="toctree-l1"><a class="reference internal" href="../dev/internals/">Ceph 内幕</a></li>
<li class="toctree-l1"><a class="reference internal" href="../governance/">项目管理</a></li>
<li class="toctree-l1"><a class="reference internal" href="../foundation/">Ceph 基金会</a></li>
<li class="toctree-l1"><a class="reference internal" href="../ceph-volume/">ceph-volume</a></li>
<li class="toctree-l1"><a class="reference internal" href="../releases/general/">Ceph 版本（总目录）</a></li>
<li class="toctree-l1"><a class="reference internal" href="../releases/">Ceph 版本（索引）</a></li>
<li class="toctree-l1"><a class="reference internal" href="../security/">Security</a></li>
<li class="toctree-l1"><a class="reference internal" href="../glossary/">Ceph 术语</a></li>
<li class="toctree-l1"><a class="reference internal" href="../jaegertracing/">Tracing</a></li>
<li class="toctree-l1"><a class="reference internal" href="../translation_cn/">中文版翻译资源</a></li>
</ul>

            
          
        </div>
        
      </div>
    </nav>

    <section data-toggle="wy-nav-shift" class="wy-nav-content-wrap">

      
      <nav class="wy-nav-top" aria-label="top navigation">
        
          <i data-toggle="wy-nav-top" class="fa fa-bars"></i>
          <a href="../">Ceph</a>
        
      </nav>


      <div class="wy-nav-content">
        
        <div class="rst-content">
        
          <div role="main" class="document" itemscope="itemscope" itemtype="http://schema.org/Article">
           <div itemprop="articleBody">
            
<div id="dev-warning" class="admonition note">
  <p class="first admonition-title">Notice</p>
  <p class="last">This document is for a development version of Ceph.</p>
</div>
  <div id="docubetter" align="right" style="padding: 5px; font-weight: bold;">
    <a href="https://pad.ceph.com/p/Report_Documentation_Bugs">Report a Documentation Bug</a>
  </div>

  
  <div class="section" id="id1">
<h1>体系结构<a class="headerlink" href="#id1" title="Permalink to this headline">¶</a></h1>
<p>Ceph 独一无二地用统一的系统提供了<strong>对象、块、和文件存储</strong>功能，
它可靠性高、管理简便、并且是自由软件。
Ceph 的强大足以改变贵公司的 IT 基础架构、和管理海量数据。
Ceph 可提供极大的伸缩性——供成千用户访问 PB 乃至 EB 级的数据。
<a class="reference internal" href="../glossary/#term-4"><span class="xref std std-term">Ceph 节点</span></a>以普通硬件和智能守护进程作为支撑点，
<a class="reference internal" href="../glossary/#term-7"><span class="xref std std-term">Ceph 存储集群</span></a>组织起了大量节点，它们之间靠相互通讯来复制数据、
并动态地重分布数据。</p>
<img alt="../_images/stack.png" src="../_images/stack.png" />
<div class="section" id="ceph">
<h2>Ceph 存储集群<a class="headerlink" href="#ceph" title="Permalink to this headline">¶</a></h2>
<p>Ceph 提供了一个可无限伸缩的 <a class="reference internal" href="../glossary/#term-7"><span class="xref std std-term">Ceph 存储集群</span></a>，它基于 <abbr title="Reliable Autonomic Distributed Object Store">RADOS</abbr> ，见论文 <a class="reference external" href="https://ceph.com/wp-content/uploads/2016/08/weil-rados-pdsw07.pdf">RADOS - A Scalable, Reliable Storage Service for Petabyte-scale Storage Clusters</a> 。</p>
<p>Ceph 存储集群包含多种类型的守护进程：</p>
<ul class="simple">
<li><p><a class="reference internal" href="../glossary/#term-24"><span class="xref std std-term">Ceph 监视器</span></a></p></li>
<li><p><a class="reference internal" href="../glossary/#term-22"><span class="xref std std-term">Ceph 对象存储守护进程</span></a></p></li>
<li><p><a class="reference internal" href="../glossary/#term-26"><span class="xref std std-term">Ceph 管理器</span></a></p></li>
<li><p><a class="reference internal" href="../glossary/#term-33"><span class="xref std std-term">Ceph 元数据服务器</span></a></p></li>
</ul>
<p>Ceph 监视器维护着集群运行图的主副本。
一个监视器集群确保了当某个监视器失效时的高可用性。
存储集群客户端向 Ceph 监视器索取集群运行图的最新副本。</p>
<p>Ceph OSD 守护进程检查自身状态、以及其它 OSD 的状态，
并报告给监视器们。</p>
<p>Ceph 管理器作为监控、编排、和插件模块的端点。</p>
<p>Ceph 元数据服务器（ MDS ）在启用了 CephFS 提供文件服务时用来管理文件的元数据。</p>
<p>存储集群的客户端和各个 <a class="reference internal" href="../glossary/#term-23"><span class="xref std std-term">Ceph OSD 守护进程</span></a>使用 CRUSH 算法高效地计算数据位置，而不是查询某个表。
它的高级功能包括：基于 <code class="docutils literal notranslate"><span class="pre">librados</span></code> 的原生存储接口、
和多种基于 <code class="docutils literal notranslate"><span class="pre">librados</span></code> 的服务接口。</p>
<div class="section" id="id2">
<h3>数据的存储<a class="headerlink" href="#id2" title="Permalink to this headline">¶</a></h3>
<p>Ceph 存储集群从 <a class="reference internal" href="../glossary/#term-35"><span class="xref std std-term">Ceph 客户端</span></a>接收数据——
不管是来自 <a class="reference internal" href="../glossary/#term-16"><span class="xref std std-term">Ceph 块设备</span></a>、 <a class="reference internal" href="../glossary/#term-13"><span class="xref std std-term">Ceph 对象存储</span></a>、
<a class="reference internal" href="../glossary/#term-18"><span class="xref std std-term">Ceph 文件系统</span></a>、还是基于 <code class="docutils literal notranslate"><span class="pre">librados</span></code> 自己实现的——并存储为 RADOS 对象。 Ceph OSD 守护进程负责在存储驱动器上处理读、写、还有复制操作。在比较老的 Filestore 后端上，每个 RADOS 对象都是在传统文件系统（通常是 XFS ）上存储的一个独立文件；在新的、默认的 BlueStore 后端上，对象以类似单体数据库的方式存储。</p>
<p>OSD 在一个扁平命名空间内把数据存储为对象（也就是没有目录层次）。对象包含一个标识符、二进制数据、和由名字/值配对组成的元数据，其语义完全取决于
<a class="reference internal" href="../glossary/#term-35"><span class="xref std std-term">Ceph 客户端</span></a>。例如， CephFS 用元数据存储文件属性，如文件所有者、创建日期、最后修改日期等等。</p>
<div class="admonition note">
<p class="admonition-title">Note</p>
<p>一个对象 ID 不止在本地唯一 ，它在整个集群内都是唯一的。</p>
</div>
</div>
<div class="section" id="index-0">
<span id="id3"></span><h3>伸缩性和高可用性<a class="headerlink" href="#index-0" title="Permalink to this headline">¶</a></h3>
<p>在传统架构里，客户端沟通中央化的组件
（如网关、中间件、 API 、前端等等），它作为一个复杂子系统的单接触点，
它引入单故障点的同时，也压制了性能和伸缩性
（就是说如果中央化组件挂了，
整个系统就挂了）。</p>
<p>Ceph 消除了集中网关，允许客户端直接和 OSD 守护进程通讯。
OSD 守护进程自动在其它 Ceph 节点上创建对象副本来确保数据安全和高可用性；
为保证高可用性，监视器也实现了集群化。
为消除中央集权制， Ceph 使用了 CRUSH 算法。</p>
<div class="section" id="crush">
<span id="index-1"></span><h4>CRUSH 简介<a class="headerlink" href="#crush" title="Permalink to this headline">¶</a></h4>
<p>Ceph 客户端和 OSD 守护进程都用 <abbr title="Controlled Replication Under Scalable Hashing">CRUSH</abbr> 算法来按需计算对象的位置信息，而不是查询某个集中的表。
和以往方法相比， CRUSH 的数据管理机制更好，
它很干脆地把某些工作丢给集群内的所有客户端和 OSD 来处理，
因此具有极大的伸缩性。 CRUSH 用智能数据复制确保弹性，
更能适应超大规模存储。
下列几段描述了 CRUSH 如何工作，更详细的机制请参阅论文：
<a class="reference external" href="https://ceph.com/wp-content/uploads/2016/08/weil-crush-sc06.pdf">CRUSH - 可控、可伸缩、分布式地归置多副本数据</a> 。</p>
</div>
<div class="section" id="index-2">
<span id="id4"></span><h4>集群运行图<a class="headerlink" href="#index-2" title="Permalink to this headline">¶</a></h4>
<p>Ceph 依赖于 Ceph 客户端和 OSD ，因为它们知道集群的拓扑，
这个拓扑由 5 张图共同描述，统称为“集群运行图”：</p>
<ol class="arabic simple">
<li><p><strong>监视器图：</strong> 包含集群的 <code class="docutils literal notranslate"><span class="pre">fsid</span></code> 、位置、名字、地址和端口，
也包括当前时间结、
此图何时创建、最近修改时间。
要查看监视器图，用 <code class="docutils literal notranslate"><span class="pre">ceph</span> <span class="pre">mon</span> <span class="pre">dump</span></code> 命令。</p></li>
<li><p><strong>OSD 图：</strong> 包含集群 <code class="docutils literal notranslate"><span class="pre">fsid</span></code> 、此图何时创建、
最近修改时间、存储池列表、副本数量、归置组数量、 OSD 列表及其状态（如 <code class="docutils literal notranslate"><span class="pre">up</span></code> 、 <code class="docutils literal notranslate"><span class="pre">in</span></code> ）。要查看OSD运行图，
用 <code class="docutils literal notranslate"><span class="pre">ceph</span> <span class="pre">osd</span> <span class="pre">dump</span></code> 命令。</p></li>
<li><p><strong>归置组图：</strong> 包含归置组版本、其时间戳、最新的 OSD 图时间结、
占满率、以及各归置组详情，像归置组 ID 、 <cite>up set</cite> 、
<cite>acting set</cite> 、 PG 状态（如 <code class="docutils literal notranslate"><span class="pre">active+clean</span></code> ），
和各存储池的数据使用情况统计。</p></li>
<li><p><strong>CRUSH 图：</strong> 包含存储设备列表、失败域树状结构
（如设备、主机、机架、行、房间、等等）、
和存储数据时如何利用此树状结构的规则。要查看 CRUSH 规则，
执行 <code class="docutils literal notranslate"><span class="pre">ceph</span> <span class="pre">osd</span> <span class="pre">getcrushmap</span> <span class="pre">-o</span> <span class="pre">{filename}</span></code> 命令；
然后用 <code class="docutils literal notranslate"><span class="pre">crushtool</span> <span class="pre">-d</span> <span class="pre">{comp-crushmap-filename}</span> <span class="pre">-o</span> <span class="pre">{decomp-crushmap-filename}</span></code>
反编译；然后就可以用 <code class="docutils literal notranslate"><span class="pre">cat</span></code> 或编辑器查看了。</p></li>
<li><p><strong>MDS 图：</strong> 包含当前 MDS 图的时间结、此图创建于何时、最近修改时间，还包含了存储元数据的存储池、元数据服务器列表、还有哪些元数据服务器是 <code class="docutils literal notranslate"><span class="pre">up</span></code> 且 <code class="docutils literal notranslate"><span class="pre">in</span></code> 的。要查看
MDS 图，执行 <code class="docutils literal notranslate"><span class="pre">ceph</span> <span class="pre">fs</span> <span class="pre">dump</span></code> 。</p></li>
</ol>
<p>各运行图维护着各自运营状态的变更， Ceph 监视器维护着一份集群运行图的主拷贝，包括集群成员、状态、变更、以及
Ceph 存储集群的整体健康状况。</p>
</div>
<div class="section" id="index-3">
<span id="id5"></span><h4>高可用监视器<a class="headerlink" href="#index-3" title="Permalink to this headline">¶</a></h4>
<p>Ceph 客户端读或写数据前必须先连接到某个 Ceph 监视器、
获得最新的集群运行图副本。一个 Ceph 存储集群只需要单个监视器就能运行，
但它就成了单故障点（即如果此监视器当机， Ceph 客户端就不能读写数据了）。</p>
<p>为增强可靠性和容错能力， Ceph 支持监视器集群；
在一个监视器集群内，延时以及其它错误会导致一到多个监视器滞后于集群的当前状态，
因此， Ceph 的各监视器例程必须就集群的当前状态达成一致。为此，
Ceph 总是使用大多数监视器（如： 1 、 2:3 、 3:5 、 4:6 等等）和 <a class="reference external" href="https://en.wikipedia.org/wiki/Paxos_(computer_science)">Paxos</a> 算法就集群的当前状态达成一致。</p>
<p>关于配置监视器的详情，见<a class="reference external" href="../rados/configuration/mon-config-ref">监视器配置参考</a>。</p>
</div>
<div class="section" id="index-4">
<span id="id6"></span><h4>高可用性认证<a class="headerlink" href="#index-4" title="Permalink to this headline">¶</a></h4>
<p>为辨明用户并防止中间人攻击， Ceph 用 <code class="docutils literal notranslate"><span class="pre">cephx</span></code> 认证系统来认证用户和守护进程。</p>
<div class="admonition note">
<p class="admonition-title">Note</p>
<p><code class="docutils literal notranslate"><span class="pre">cephx</span></code> 协议不解决传输加密（如 SSL/TLS ）、或者存储加密问题。</p>
</div>
<p>Cephx 用共享密钥来认证，即客户端和监视器集群各自都有客户端密钥的副本。这样的认证协议使两边的参与者无需出示密钥就能相互向对方证明自己。这样就能做到相互认证，
就是说集群确信用户拥有密钥、
而且用户相信集群有密钥的副本。</p>
<p>Ceph 一个主要伸缩功能就是避免了对象存储的中央接口，
这就要求 Ceph 客户端能直接和 OSD 交互。
为了保护数据， Ceph 提出了自己的 <code class="docutils literal notranslate"><span class="pre">cephx</span></code> 认证系统，
它可以认证运行 Ceph 客户端的用户。
<code class="docutils literal notranslate"><span class="pre">cephx</span></code> 协议运行机制类似 <a class="reference external" href="https://en.wikipedia.org/wiki/Kerberos_(protocol)">Kerberos</a> 。</p>
<p>用户/参与者通过调用 Ceph 客户端来联系监视器，不像 Kerberos ，
每个监视器都能认证用户、发布密钥，所以使用 <code class="docutils literal notranslate"><span class="pre">cephx</span></code> 时不会有单点故障或瓶颈。监视器返回一个类似 Kerberos 票据的认证数据结构，
它包含一个可用于获取 Ceph 服务的会话密钥，
会话密钥是用户的永久私钥自加密过的，
只有此用户能从 Ceph 监视器请求服务。
客户端用会话密钥向监视器请求需要的服务，
然后监视器给客户端一个凭证用以向实际持有数据的 OSD 认证。
Ceph 的监视器和 OSD 共享相同的密钥，
所以集群内任何 OSD 或元数据服务器都认可客户端从监视器获取的凭证，
像 Kerberos 一样 <code class="docutils literal notranslate"><span class="pre">cephx</span></code> 凭证也会过期，
以使攻击者不能用暗中得到的过期凭证或会话密钥。
只要用户的私钥过期前没有泄露 ，
这种认证形式就可防止中间线路攻击者以别人的 ID 发送垃圾消息、
或修改用户的正常消息。</p>
<p>要使用 <code class="docutils literal notranslate"><span class="pre">cephx</span></code> ，管理员必须先设置好用户。
在下面的图解里， <code class="docutils literal notranslate"><span class="pre">client.admin</span></code> 用户从命令行调用 <code class="docutils literal notranslate"><span class="pre">ceph</span> <span class="pre">auth</span> <span class="pre">get-or-create-key</span></code>
来生成一个用户及其密钥， Ceph 的认证子系统生成了用户名和密钥、
副本存到监视器然后把此用户的密钥回传给 <code class="docutils literal notranslate"><span class="pre">client.admin</span></code> 用户，
也就是说客户端和监视器共享着相同的密钥。</p>
<div class="admonition note">
<p class="admonition-title">Note</p>
<p><code class="docutils literal notranslate"><span class="pre">client.admin</span></code> 用户必须以安全方式把此用户 ID 和密钥交给用户。</p>
</div>
<p>要和监视器认证，客户端得把用户名传给监视器，
然后监视器生成一个会话密钥、并且用此用户的密钥加密它，
然后把加密的凭证回传给客户端，
客户端用共享密钥解密载荷就可获取会话密钥。
会话密钥在当前会话中标识了此用户，
客户端再用此会话密钥签署过的用户名请求一个凭证，
监视器生成一个凭证、用客户端的密钥加密它，
然后回传给客户端，客户端解密此凭证，
然后用它签署连接集群内 OSD 和元数据服务器的请求。</p>
<p><code class="docutils literal notranslate"><span class="pre">cephx</span></code> 协议认证客户端机器和 Ceph 服务器间正在进行的通讯，
二者间认证完成后的每条消息都用凭证签署过，
监视器、 OSD 、元数据服务器都可以用它们共享的密钥来校验这些消息。</p>
<p>认证提供的保护位于 Ceph 客户端和服务器间，
没有扩展到 Ceph 客户端之外。如果用户从远程主机访问 Ceph 客户端，
Ceph 认证就不管用了，它不会影响到用户主机和客户端主机间的通讯。</p>
<p>关于如何配置，请参考 <a class="reference external" href="../rados/configuration/auth-config-ref">Cephx 配置指南</a>；关于用户管理细节，请参考<a class="reference external" href="../rados/operations/user-management">用户管理</a>。</p>
</div>
<div class="section" id="index-5">
<span id="id7"></span><h4>智能程序支撑超大规模<a class="headerlink" href="#index-5" title="Permalink to this headline">¶</a></h4>
<p>在很多集群化体系结构中，集群成员的主要目的都相似，
集中式接口知道它能访问哪些节点，
然后此中央接口通过一个两级调度把服务调给客户端，
在 PB 到 EB 级系统中这个调度系统必将成为<strong>最大</strong>的瓶颈。</p>
<p>Ceph 消除了此瓶颈：其 OSD 守护进程和客户端都能感知集群，
比如 Ceph 客户端、各 OSD 守护进程都知道集群内有哪些节点，
这样 OSD 就能直接和其它 OSD 守护进程和监视器们通讯。
另外， Ceph 客户端也能直接和 OSD 守护进程交互。</p>
<p>Ceph 客户端、监视器和 OSD 守护进程可以相互直接交互，
这意味着 OSD 可以利用本地节点的 CPU 和内存执行那些有可能拖垮中央服务器的任务。
这种设计均衡了计算资源，带来几个好处：</p>
<ol class="arabic">
<li><p><strong>OSD 直接服务于客户端：</strong> 由于任何网络设备都有最大并发连接上限，
规模巨大时中央化的系统其物理局限性就暴露了。
Ceph 允许客户端直接和 OSD 节点联系，
这在消除单故障点的同时，
提升了性能和系统总容量。
Ceph 客户端可按需维护和某 OSD 的会话，
而不是一中央服务器。</p></li>
<li><p><strong>OSD 成员和状态：</strong> Ceph OSD 加入集群后会持续报告自己的状态。
在底层， OSD 状态为 <code class="docutils literal notranslate"><span class="pre">up</span></code> 或 <code class="docutils literal notranslate"><span class="pre">down</span></code> ，
反映它是否在运行、能否提供服务。
如果一 OSD 状态为 <code class="docutils literal notranslate"><span class="pre">down</span></code> 且 <code class="docutils literal notranslate"><span class="pre">in</span></code> ，
表明 OSD 守护进程可能失败了；
如果一 OSD 守护进程没在运行（比如崩溃了），
它就不能亲自向监视器报告自己是 <code class="docutils literal notranslate"><span class="pre">down</span></code> 的。
所有 OSD 都会周期性地向 Ceph 监视器发送消息
（ luminous 之前是 <code class="docutils literal notranslate"><span class="pre">MPGStats</span></code> ， luminous 起新增 <code class="docutils literal notranslate"><span class="pre">MOSDBeacon</span></code> ）；
如果 Ceph 监视器在配置的周期内没看到这消息，就把它标记为 <code class="docutils literal notranslate"><span class="pre">down</span></code> ，
然而，这只是个故障双保险机制。正常情况下，
Ceph OSD 守护进程会判断邻居 OSD 是否倒下、并报告给监视器（们）。
这样就能保证 Ceph 监视器始终是个轻量级进程。
详情见<a class="reference external" href="../rados/operations/monitoring-osd-pg/#monitoring-osds">监控 OSD</a> 和<a class="reference external" href="../rados/configuration/mon-osd-interaction">心跳</a>。</p></li>
<li><p><strong>数据洗刷：</strong> 作为维护数据一致性和清洁度的一部分，
Ceph OSD 守护进程能洗刷对象。就是说，
Ceph OSD 守护进程能比对本地的对象元数据和其它 OSD 上的副本。
洗刷是以归置组为单位进行的；
通常（每天执行）可以捕捉到尺寸和其它元数据不一致的地方。
Ceph OSD 守护进程也会做更深层次的洗刷，
即逐位比对对象中的数据和它们的校验和，
深度洗刷（通常每周执行）用于找出轻度洗刷时没发现的硬盘坏扇区。
关于洗刷配置见<a class="reference external" href="../rados/configuration/osd-config-ref#scrubbing">数据洗刷</a>。</p></li>
<li><p><strong>复制：</strong> 和 Ceph 客户端一样， OSD 也用 CRUSH 算法，
但用于计算副本存到哪里（也用于重均衡）。
一个典型的情形是，
一客户端用 CRUSH 算法算出对象应存到哪里，
并把对象映射到存储池和归置组，
然后查找 CRUSH 图来确定此归置组的主 OSD 。</p>
<p>客户端把对象写入目标归置组的主 OSD ，
然后这个主 OSD 再用它的 CRUSH 图副本找出用于放对象副本的第二、第三个 OSD ，
并把数据复制到适当的归置组所对应的第二、第三 OSD
（要多少副本就有多少 OSD ），
最终，确认数据成功存储后反馈给客户端。</p>
</li>
</ol>
<p>有了做副本的能力， OSD 守护进程就可以减轻客户端的复制压力，
同时保证了数据的高可靠性和安全性。</p>
</div>
</div>
<div class="section" id="id8">
<h3>动态集群管理<a class="headerlink" href="#id8" title="Permalink to this headline">¶</a></h3>
<p>在<a class="reference internal" href="#id3">伸缩性和高可用性</a>一节，我们解释了 Ceph 如何用 CRUSH 、
集群感知性和智能 OSD 守护进程来扩展和维护高可靠性。
Ceph 的关键设计是自治，自修复、智能的 OSD 守护进程。
让我们深入了解下 CRUSH 如何运作，
现代云存储基础设施如何动态地放置数据、重均衡、从错误中恢复。</p>
<div class="section" id="index-6">
<span id="id9"></span><h4>关于存储池<a class="headerlink" href="#index-6" title="Permalink to this headline">¶</a></h4>
<p>Ceph 存储系统支持“池”概念，它是存储对象的逻辑分区。</p>
<p>Ceph 客户端从监视器获取一张<a class="reference internal" href="#id4">集群运行图</a>，并把对象写入存储池。
存储池的 <code class="docutils literal notranslate"><span class="pre">size</span></code> 或副本数、 CRUSH 规则和归置组数量决定着
Ceph 如何放置数据。</p>
<p>存储池至少可设置以下参数：</p>
<ul class="simple">
<li><p>对象的所有权/访问权限；</p></li>
<li><p>归置组数量；以及，</p></li>
<li><p>使用的 CRUSH 规则。</p></li>
</ul>
<p>详情见<a class="reference external" href="../rados/operations/pools#set-pool-values">调整存储池</a>。</p>
</div>
<div class="section" id="pg-osd">
<span id="index-7"></span><h4>PG 映射到 OSD<a class="headerlink" href="#pg-osd" title="Permalink to this headline">¶</a></h4>
<p>各归置组都有很多归置组， CRUSH 动态的把它们映射到 OSD 。
Ceph 客户端要存对象时， CRUSH 将把各对象映射到某个归置组。</p>
<p>把对象映射到归置组在 OSD 和客户端间创建了一个间接层。
由于 Ceph 集群必须能增大或缩小、并动态地重均衡。
如果要让客户端“知道”哪个 OSD 有哪个对象，
就会导致客户端和 OSD 密耦合；
相反， CRUSH 算法把一堆对象映射到一归置组、
然后再把各归置组映射到一或多个 OSD ，
这一间接层可以让 Ceph 在 OSD 守护进程和底层设备上线时动态地重均衡。
下列图表描述了如何用 CRUSH 把对象映射到归置组、
再把归置组映射到 OSD 。</p>
<p>有了集群运行图副本和 CRUSH 算法，
客户端就能精确地计算出到哪个 OSD 读、写某特定对象。</p>
</div>
<div class="section" id="pg-id">
<span id="index-8"></span><h4>计算 PG ID<a class="headerlink" href="#pg-id" title="Permalink to this headline">¶</a></h4>
<p>Ceph 客户端绑定到某监视器时，会索取最新的<a class="reference internal" href="#id4">集群运行图</a>副本，
有了此图，客户端就能知道集群内的所有监视器、 OSD 、和元数据服务器。
<strong>然而它对对象的位置一点也不了解。</strong></p>
<blockquote class="epigraph">
<div><p>对象位置是计算出来的。</p>
</div></blockquote>
<p>客户端只需输入对象 ID 和存储池，
此事简单： Ceph 把数据存在某存储池（如 liverpool ）中。
当客户端想要存命名对象（如 john 、 paul 、 george 、 ringo 等等）时，
它用对象名计算归置组（一个哈希值）、 OSD 号、存储池。
Ceph 按下列步骤计算 PG ID 。</p>
<ol class="arabic simple">
<li><p>客户端输入存储池名和对象 ID （如 pool=”liverpool” 和
object-id=”john” ）；</p></li>
<li><p>CRUSH 拿到对象 ID 并哈希它；</p></li>
<li><p>CRUSH 用 OSD 数（如 <code class="docutils literal notranslate"><span class="pre">58</span></code> ）对哈希值取模，这就是归置组 ID ；</p></li>
<li><p>CRUSH 根据存储池名取得存储池 ID （如liverpool = <code class="docutils literal notranslate"><span class="pre">4</span></code> ）；</p></li>
<li><p>CRUSH 把存储池 ID 加到PG ID（如 <code class="docutils literal notranslate"><span class="pre">4.58</span></code> ）之前。</p></li>
</ol>
<p>计算对象位置远快于查询定位， <abbr title="Controlled Replication Under Scalable Hashing">CRUSH</abbr>
算法允许客户端计算对象<em>应该</em>存到哪里，并允许客户端连接存储此主 OSD 来存储或检索对象。</p>
</div>
<div class="section" id="index-9">
<span id="id10"></span><h4>互联和子集<a class="headerlink" href="#index-9" title="Permalink to this headline">¶</a></h4>
<p>在前面的章节中，我们注意到 OSD 守护进程相互检查心跳并回馈给监视器；
它们的另一行为叫“互联（ peering ）”，
这是一种把一归置组内所有对象（及其元数据）所在的 OSD
带到一致状态的过程。
事实上， OSD 守护进程会向监视器<a class="reference external" href="../rados/configuration/mon-osd-interaction#osds-report-peering-failure">报告互联失败</a>，
互联问题一般会自行恢复，
然而如果问题一直持续，
你也许得参照<a class="reference external" href="../rados/troubleshooting/troubleshooting-pg#placement-group-down-peering-failure">互联失败排障</a>解决。</p>
<div class="admonition note">
<p class="admonition-title">Note</p>
<p>对状态达成一致并不意味着 PG 持有最新内容。</p>
</div>
<p>Ceph 存储集群被设计为至少存储两份（即 <code class="docutils literal notranslate"><span class="pre">size</span> <span class="pre">=</span> <span class="pre">2</span></code> ），
这是保证数据安全的最小需求。
为保证高可靠性， Ceph 存储集群应该至少保存一对象的两个副本
（如 <code class="docutils literal notranslate"><span class="pre">size</span> <span class="pre">=</span> <span class="pre">3</span></code> 且 <code class="docutils literal notranslate"><span class="pre">min</span> <span class="pre">size</span> <span class="pre">=</span> <span class="pre">2</span></code> ），
这样才能在 <code class="docutils literal notranslate"><span class="pre">degraded</span></code> 状态下持续运行的同时、仍然能维持数据安全。</p>
<p>回想前面<a class="reference internal" href="#id7">智能程序支撑超大规模</a>中的图表，
我们没明确地提 OSD 守护进程的名字（如 <code class="docutils literal notranslate"><span class="pre">osd.0</span></code> 、 <code class="docutils literal notranslate"><span class="pre">osd.1</span></code> 等等），
而是称之为<em>主</em>、<em>次</em>、以此类推。按惯例，
<em>主 OSD</em> 是 <em>Acting set</em> 中的第一个 OSD ，
而且它负责协调各归置组的互联进程，
所以称之为<em>主 OSD</em> ；也<em>只有它</em>会接受客户端到某归置组的初始写入请求。</p>
<p>当一系列 OSD 负责一归置组时，
这一系列的 OSD 就成为一个 <em>Acting Set</em> 。
一个 <em>acting set</em> 可对应当前负责此归置组的一些 OSD ，
或者说一些 OSD 在一些时间结上负责某个特定归置组。</p>
<p>OSD 守护进程作为 <em>acting set</em> 的一部分，
不一定总在 <code class="docutils literal notranslate"><span class="pre">up</span></code> 状态。当一 OSD 在 <em>acting set</em> 中是 <code class="docutils literal notranslate"><span class="pre">up</span></code> 状态时，
它就是 <code class="docutils literal notranslate"><span class="pre">up</span> <span class="pre">set</span></code> 的一部分。 <code class="docutils literal notranslate"><span class="pre">up</span> <span class="pre">set</span></code> 是个重要特征，
因为某 OSD 失败时 Ceph 会把 PG 映射到其他 OSD 。</p>
<div class="admonition note">
<p class="admonition-title">Note</p>
<p>在某 PG 的 <em>acting set</em> 中包含了 <code class="docutils literal notranslate"><span class="pre">osd.25</span></code> 、 <code class="docutils literal notranslate"><span class="pre">osd.32</span></code>
和 <code class="docutils literal notranslate"><span class="pre">osd.61</span></code> ，第一个 <code class="docutils literal notranslate"><span class="pre">osd.25</span></code> 是主 OSD ，如果它失败了，
第二个 <code class="docutils literal notranslate"><span class="pre">osd.32</span></code> 就成为主 OSD ，
<code class="docutils literal notranslate"><span class="pre">osd.25</span></code> 会被移出 <em>up set</em> 。</p>
</div>
</div>
<div class="section" id="index-10">
<span id="id11"></span><h4>重均衡<a class="headerlink" href="#index-10" title="Permalink to this headline">¶</a></h4>
<p>你向 Ceph 存储集群新增一 OSD 守护进程时，
集群运行图就要用新增的 OSD 更新。回想<a class="reference internal" href="#pg-id">计算 PG ID</a> ，
这个动作会更改集群运行图，
因此也改变了对象位置，因为计算时的输入条件变了。
下面的图描述了重均衡过程（此图很粗略，
因为在大型集群里变动幅度小的多），
是其中的一些而不是所有 PG 都从已有 OSD （ OSD 1 和 2 ）迁移到新 OSD （ OSD 3 ）。
即使在重均衡中， CRUSH 都是稳定的，很多归置组仍维持最初的配置，
且各 OSD 都腾出了些空间，
所以重均衡完成后新 OSD 上不会有到突增负载。</p>
</div>
<div class="section" id="index-11">
<span id="id12"></span><h4>数据一致性<a class="headerlink" href="#index-11" title="Permalink to this headline">¶</a></h4>
<p>作为维护数据一致性和清洁度的一种职能，
OSD 也会洗刷归置组内的对象，也就是说，
OSD 会比较归置组内位于不同 OSD 上同一对象副本的元数据。洗刷（通常每天执行）是为捕获 OSD 缺陷和文件系统错误，通常能反映出硬件问题；OSD 也会进行深度洗刷：按位比较对象内的数据；深度洗刷（通常每周执行）是为了捕捉在轻度洗刷时没有出现的、驱动器上的坏块。</p>
<p>关于数据洗刷的配置见<a class="reference external" href="../rados/configuration/osd-config-ref#scrubbing">数据洗刷</a>。</p>
</div>
</div>
<div class="section" id="index-12">
<span id="id13"></span><h3>纠删编码<a class="headerlink" href="#index-12" title="Permalink to this headline">¶</a></h3>
<p>纠删码存储池把各对象存储为 <code class="docutils literal notranslate"><span class="pre">K+M</span></code> 个数据块，
其中有 <code class="docutils literal notranslate"><span class="pre">K</span></code> 个数据块和 <code class="docutils literal notranslate"><span class="pre">M</span></code> 个编码块。
此存储池的尺寸为 <code class="docutils literal notranslate"><span class="pre">K+M</span></code> ，这样各块被存储到位于 acting set 中的 OSD ，
块的位置也作为对象属性保存下来了。</p>
<p>比如，可以创建一个使用 5 个 OSD 的纠删码存储池（ <code class="docutils literal notranslate"><span class="pre">K+M</span> <span class="pre">=</span> <span class="pre">5</span></code> ）并能容忍其中两个丢失（ <code class="docutils literal notranslate"><span class="pre">M</span> <span class="pre">=</span> <span class="pre">2</span></code> ）。</p>
<div class="section" id="id14">
<h4>读出和写入编码块<a class="headerlink" href="#id14" title="Permalink to this headline">¶</a></h4>
<p>当包含 <code class="docutils literal notranslate"><span class="pre">ABCDEFGHI</span></code> 的对象 <strong>NYAN</strong> 被写入存储池时，
纠删编码功能把内容分割为三个数据块，只是简单地切割为三份：
第一份包含 <code class="docutils literal notranslate"><span class="pre">ABC</span></code> 、第二份是 <code class="docutils literal notranslate"><span class="pre">DEF</span></code> 、最后是 <code class="docutils literal notranslate"><span class="pre">GHI</span></code> ，
若内容长度不是 <code class="docutils literal notranslate"><span class="pre">K</span></code> 的倍数则需填充；
此功能还会创建两个编码块：第四个是 <code class="docutils literal notranslate"><span class="pre">YXY</span></code> 、第五个是 <code class="docutils literal notranslate"><span class="pre">QGC</span></code> ，
各块分别存入 acting set 中的 OSD 内。
这些块以相同的名字（ <strong>NYAN</strong> ）存入对象、但是位于不同的 OSD 上；
分块顺序也必须保留，被存储为对象的一个属性（ <code class="docutils literal notranslate"><span class="pre">shard_t</span></code> ）追加到名字后面。
包含 <code class="docutils literal notranslate"><span class="pre">ABC</span></code> 的块 1 存储在 <strong>OSD5</strong> 上、
包含 <code class="docutils literal notranslate"><span class="pre">YXY</span></code> 的块 4 存储在 <strong>OSD3</strong> 上。</p>
<p>从纠删码存储池中读取 <strong>NYAN</strong> 对象时，解码功能会读取三个块：
包含 <code class="docutils literal notranslate"><span class="pre">ABC</span></code> 的块 1 ，包含 <code class="docutils literal notranslate"><span class="pre">GHI</span></code> 的块 3 和包含 <code class="docutils literal notranslate"><span class="pre">YXY</span></code> 的块 4 ，然后重建对象的原始内容 <code class="docutils literal notranslate"><span class="pre">ABCDEFGHI</span></code> 。
解码功能被告知块 2 和 5 丢失了（被称为“擦除”），
块 5 不可读是因为 <strong>OSD4</strong> 出局了；
<strong>OSD2</strong> 是最慢的，其数据未被采纳。
只要有三块读出就可以成功调用解码功能。</p>
</div>
<div class="section" id="id15">
<h4>被中断的完全重写<a class="headerlink" href="#id15" title="Permalink to this headline">¶</a></h4>
<p>在纠删码存储池中， up set 中的主 OSD 接受所有写操作，
它负责把载荷编码为 <code class="docutils literal notranslate"><span class="pre">K+M</span></code> 个块并发送给其它 OSD 。
它也负责维护归置组日志的一份权威版本。</p>
<p>在下图中，已创建了一个参数为 <code class="docutils literal notranslate"><span class="pre">K</span> <span class="pre">=</span> <span class="pre">2,</span> <span class="pre">M</span> <span class="pre">=</span> <span class="pre">1</span></code> 的纠删编码归置组，存储在三个 OSD 上，两个存储 <code class="docutils literal notranslate"><span class="pre">K</span></code> 、
一个存 <code class="docutils literal notranslate"><span class="pre">M</span></code> 。此归置组的 acting set 由 <strong>OSD 1</strong> 、 <strong>OSD 2</strong> 、
<strong>OSD 3</strong> 组成。一个对象已被编码并存进了各 OSD ：
块 <code class="docutils literal notranslate"><span class="pre">D1v1</span></code> （即数据块号为 1 ，版本为 1 ）在 <strong>OSD 1</strong> 上、
<code class="docutils literal notranslate"><span class="pre">D2v1</span></code> 在 <strong>OSD 2</strong> 上、 <code class="docutils literal notranslate"><span class="pre">C1v1</span></code> （即编码块号为 1 ，
版本为 1 ）在 <strong>OSD 3</strong> 上。各 OSD 上的归置组日志都相同（即 <code class="docutils literal notranslate"><span class="pre">1,1</span></code> ，表明 epoch 为 1 ，版本为 1 ）。</p>
<p><strong>OSD 1</strong> 是主的，它从客户端收到了 <strong>WRITE FULL</strong> 请求，这意味着净载荷将会完全取代此对象，而非部分覆盖。此对象的版本 2 （ v2 ）将被创建以取代版本 1 （ v1 ）。
<strong>OSD 1</strong> 把净载荷编码为三块： <code class="docutils literal notranslate"><span class="pre">D1v2</span></code> （即数据块号 1 、版本 2 ）将存入 <strong>OSD 1</strong> 、
<code class="docutils literal notranslate"><span class="pre">D2v2</span></code> 在 <strong>OSD 2</strong> 上、
<code class="docutils literal notranslate"><span class="pre">C1v2</span></code> （即编码块号 1 版本 2 ）在 <strong>OSD 3</strong> 上，各块分别被发往目标 OSD ，包括主 OSD ，它除了存储块还负责处理写操作和维护归置组日志的权威版本。当某个 OSD 收到写入块的指令消息后，它也会新建一条归置组日志来反映变更，比如，在 <strong>OSD 3</strong> 存储 <code class="docutils literal notranslate"><span class="pre">C1v2</span></code> 时它也会把 <code class="docutils literal notranslate"><span class="pre">1,2</span></code>
（即 epoch 为 1 、版本为 2 ）写入它自己的日志。因为 OSD 们是异步工作的，当某些块还“飞着”时（像 <code class="docutils literal notranslate"><span class="pre">D2v2</span></code> ），其它的可能已经被确认并持久化到驱动器上了（像 <code class="docutils literal notranslate"><span class="pre">C1v1</span></code> 和 <code class="docutils literal notranslate"><span class="pre">D1v1</span></code> ）。</p>
<p>如果一切顺利，各块被证实已在 acting set 中的 OSD 上了，
日志的 <code class="docutils literal notranslate"><span class="pre">last_complete</span></code> 指针就会从 <code class="docutils literal notranslate"><span class="pre">1,1</span></code> 改为指向 <code class="docutils literal notranslate"><span class="pre">1,2</span></code> 。</p>
<p>最后，用于存储对象前一版本的文件就可以删除了：
<strong>OSD 1</strong> 上的 <code class="docutils literal notranslate"><span class="pre">D1v1</span></code> 、 <strong>OSD 2</strong> 上的 <code class="docutils literal notranslate"><span class="pre">D2v1</span></code> 和
<strong>OSD 3</strong> 上的 <code class="docutils literal notranslate"><span class="pre">C1v1</span></code> 。</p>
<p>但是意外发生了，如果 <strong>OSD 1</strong> 挂了、同时 <code class="docutils literal notranslate"><span class="pre">D2v2</span></code> 仍飞着，
此对象的版本 2 一部分已被写入了： <strong>OSD 3</strong> 有一块但是不足以恢复；
它丢失了两块： <code class="docutils literal notranslate"><span class="pre">D1v2</span></code> 和 <code class="docutils literal notranslate"><span class="pre">D2v2</span></code> ，并且纠删编码参数
<code class="docutils literal notranslate"><span class="pre">K</span> <span class="pre">=</span> <span class="pre">2</span></code> 、 <code class="docutils literal notranslate"><span class="pre">M</span> <span class="pre">=</span> <span class="pre">1</span></code> 要求至少有两块可用才能重建出第三块。
<strong>OSD 4</strong> 成为新的主 OSD ，它发现 <code class="docutils literal notranslate"><span class="pre">last_complete</span></code> 日志条目（即在此条目之前，已知所有对象都位于所有前任 acting set 中的
OSD 上、且可用）是 <code class="docutils literal notranslate"><span class="pre">1,1</span></code> 那么它将是新权威日志的头条。</p>
<p>在 <strong>OSD 3</strong> 上发现的日志条目 1,2 与 <strong>OSD 4</strong> 上新的权威日志有分歧：
它将被忽略、且包含 <code class="docutils literal notranslate"><span class="pre">C1v2</span></code> 块的文件也被删除。
<code class="docutils literal notranslate"><span class="pre">D1v1</span></code> 块将在洗刷期间通过纠删码库的 <code class="docutils literal notranslate"><span class="pre">decode</span></code> 解码功能重建，
并存储到新的主 <strong>OSD 4</strong> 上。</p>
<p>详情见<a class="reference external" href="https://github.com/ceph/ceph/blob/40059e12af88267d0da67d8fd8d9cd81244d8f93/doc/dev/osd_internals/erasure_coding/developer_notes.rst">纠删码笔记</a>。</p>
</div>
</div>
<div class="section" id="id16">
<h3>缓存分级<a class="headerlink" href="#id16" title="Permalink to this headline">¶</a></h3>
<p>对于后端存储层上的部分热点数据，
缓存层能向 Ceph 客户端提供更好的 IO 性能。
缓存分层包括创建由相对高速、昂贵的存储设备（如固态硬盘）组成的存储池，并配置为缓存层；
以及一个后端存储池，可以用纠删码编码的或者相对低速、便宜的设备，
作为经济存储层。 Ceph 对象管理器会决定往哪里放置对象，
分层代理决定何时把缓存层的对象刷回后端存储层。
所以缓存层和后端存储层对 Ceph 客户端来说是完全透明的。</p>
<p>详情见<a class="reference external" href="../rados/operations/cache-tiering">缓存分级</a>。请注意，分级缓存需要一定的技巧，现在还不建议采用。</p>
</div>
<div class="section" id="index-13">
<span id="id17"></span><h3>扩展 Ceph<a class="headerlink" href="#index-13" title="Permalink to this headline">¶</a></h3>
<p>你可以用 ‘Ceph Classes’ 共享对象类来扩展 Ceph 功能，
Ceph 会动态地载入位于 <code class="docutils literal notranslate"><span class="pre">osd</span> <span class="pre">class</span> <span class="pre">dir</span></code> 目录下的 <code class="docutils literal notranslate"><span class="pre">.so</span></code> 类文件（即默认的 <code class="docutils literal notranslate"><span class="pre">$libdir/rados-classes</span></code> ）。如果你实现了一个类，
就可以创建新的对象方法去调用 Ceph 对象存储内的原生方法、
或者公用库或自建库里的其它类方法。</p>
<p>写入时， Ceph 类能调用原生或类方法，对入栈数据执行任意操作、
生成最终写事务，
并由 Ceph 原子地应用。</p>
<p>读出时， Ceph 类能调用原生或类方法，对出栈数据执行任意操作、
把数据返回给客户端。</p>
<div class="topic">
<p class="topic-title">Ceph 类实例</p>
<p>一个为内容管理系统写的类可能要实现如下功能，
它要展示特定尺寸和长宽比的位图，
所以入栈图片要裁剪为特定长宽比、
缩放它、并嵌入个不可见的版权或水印用于保护知识产权；
然后把生成的位图保存为对象。</p>
</div>
<p>典型的实现见 <code class="docutils literal notranslate"><span class="pre">src/objclass/objclass.h</span></code> 、
<code class="docutils literal notranslate"><span class="pre">src/fooclass.cc</span></code> 、和 <code class="docutils literal notranslate"><span class="pre">src/barclass</span></code> 。</p>
</div>
<div class="section" id="id18">
<h3>小结<a class="headerlink" href="#id18" title="Permalink to this headline">¶</a></h3>
<p>Ceph 存储集群是动态的——像个生物体。
尽管很多存储应用不能完全利用一台普通服务器上的 CPU 和 RAM 资源，
但是 Ceph 能。从心跳到互联、到重均衡、再到错误恢复，
Ceph 都把客户端（和中央网关，但在 Ceph 架构中不存在）解脱了，
用 OSD 的计算资源完成此工作。
参考前面的<a class="reference external" href="../start/hardware-recommendations">硬件推荐</a>和<a class="reference external" href="../rados/configuration/network-config-ref">网络配置参考</a>理解前述概念，
就不难理解 Ceph 如何利用计算资源了。</p>
</div>
</div>
<div class="section" id="index-14">
<span id="id19"></span><h2>Ceph 协议<a class="headerlink" href="#index-14" title="Permalink to this headline">¶</a></h2>
<p>Ceph 客户端用原生协议和存储集群交互，
Ceph 把此功能封装进了 <code class="docutils literal notranslate"><span class="pre">librados</span></code> 库，这样你就能创建自己的定制客户端了，
下图描述了基本架构。</p>
<div class="section" id="librados">
<h3>原生协议和 <code class="docutils literal notranslate"><span class="pre">librados</span></code><a class="headerlink" href="#librados" title="Permalink to this headline">¶</a></h3>
<p>现代程序都需要可异步通讯的简单对象存储接口。
Ceph 存储集群提供了一个有异步通讯能力的简单对象存储接口，
此接口提供了直接写入、并行访问集群的功能。</p>
<ul class="simple">
<li><p>存储池操作；</p></li>
<li><p>快照和写时复制克隆；</p></li>
<li><p>读/写对象；
- 创建或删除；
- 整个对象或某个字节范围；
- 追加或裁截；</p></li>
<li><p>创建/设置/获取/删除扩展属性；</p></li>
<li><p>创建/设置/获取/删除键/值对；</p></li>
<li><p>混合操作和双重确认；</p></li>
<li><p>对象类。</p></li>
</ul>
</div>
<div class="section" id="index-15">
<span id="id20"></span><h3>对象关注/通知<a class="headerlink" href="#index-15" title="Permalink to this headline">¶</a></h3>
<p>客户端可以注册对某个对象的持续兴趣，
并使到主 OSD 的会话保持活跃。客户端可以发送一通知消息和载荷给所有关注者、
并可收集关注者的接收通知。
这个功能使得客户端可把任意对象用作同步/通讯通道。</p>
</div>
<div class="section" id="index-16">
<span id="id21"></span><h3>数据条带化<a class="headerlink" href="#index-16" title="Permalink to this headline">¶</a></h3>
<p>存储设备都有吞吐量限制，它会影响性能和伸缩性，
所以存储系统一般都支持<a class="reference external" href="https://en.wikipedia.org/wiki/Data_striping">条带化</a>（把连续的信息分段存储于多个设备）以增加吞吐量和性能。
数据条带化最常见于 <a class="reference external" href="https://en.wikipedia.org/wiki/RAID">RAID</a> 中，
RAID 中最接近 Ceph 条带化方式的是 <a class="reference external" href="https://en.wikipedia.org/wiki/RAID_0#RAID_0">RAID 0</a> 、或者条带化的卷，
Ceph 的条带化提供了像 RAID 0 一样的吞吐量、
像 N 路 RAID 镜像一样的可靠性、和更快的恢复。</p>
<p>Ceph 提供了三种类型的客户端：块设备、文件系统和对象存储。
一个 Ceph 客户端把展现给用户的数据格式（一块设备映像、
REST 风格对象、 CephFS 文件系统目录）转换为可存储于
Ceph 存储集群的对象。</p>
<div class="admonition tip">
<p class="admonition-title">Tip</p>
<p>在 Ceph 存储集群内存储的那些对象是没条带化的。
Ceph 对象存储、 Ceph 块设备、和 Ceph 文件系统把他们的数据条带化为 Ceph 存储集群内的对象，客户端通过 <code class="docutils literal notranslate"><span class="pre">librados</span></code>
直接写入 Ceph 存储集群前必须先自己条带化（和并行 I/O ）才能享用这些优势。</p>
</div>
<p>最简单的 Ceph 条带化格式就是拆分为一个对象。
Ceph 客户端分散地把条带单元写入 Ceph 存储集群的对象，
直到对象容量达到上限，才会再创建另一个对象存储未完的数据。
这种最简单的条带化对小个儿的块设备映像、
S3 、 Swift 对象或 CephFS 文件来说也许足够了；
然而这种简单的形式不能最大化 Ceph 在归置组间分布数据的能力，
也不能最大化性能。下图描述了条带化的最简形式：</p>
<p>如果要处理大尺寸图像、大个 S3 或 Swift 对象（如视频）、
或大个的 CephFS 目录，你就能看到条带化到一个对象集合内的多个对象能带来显著的读/写性能提升；
当客户端能把条带单元并行地写入相应对象时，
才会有优越的写性能。因为对象映射到了很多不同的归置组、
然后对应不同 OSD ，每个写入操作都可以并行地以最大速度执行。
到驱动器的写入受限于磁头移动（如每次寻道要 6ms ）、
单个存储驱动器的带宽（如 100MB/s ），
Ceph 把写入散布到多个对象（它们映射到了不同归置组和 OSD ），
这样可减少每个驱动器的寻道次数、
并联合多个驱动器的吞吐量，
以达到更高的写（或读）速度。</p>
<div class="admonition note">
<p class="admonition-title">Note</p>
<p>条带化独立于对象复制。因为 CRUSH 会在 OSD 间复制对象，数据条带是自动被复制的。</p>
</div>
<p>在下图中，客户端数据条带化到一个对象集
（下图中的 <code class="docutils literal notranslate"><span class="pre">object</span> <span class="pre">set</span> <span class="pre">1</span></code> ），它包含 4 个对象，其中，
第一个条带单元是 <code class="docutils literal notranslate"><span class="pre">object</span> <span class="pre">0</span></code> 的 <code class="docutils literal notranslate"><span class="pre">stripe</span> <span class="pre">unit</span> <span class="pre">0</span></code> 、
第四个条带是 <code class="docutils literal notranslate"><span class="pre">object</span> <span class="pre">3</span></code> 的 <code class="docutils literal notranslate"><span class="pre">stripe</span> <span class="pre">unit</span> <span class="pre">3</span></code> ，
写完第四个条带，客户端要确认对象集是否满了。
如果对象集没满，客户端再从第一个对象起写入条带（下图中的 <code class="docutils literal notranslate"><span class="pre">object</span> <span class="pre">0</span></code> ）；如果对象集满了，
客户端就得创建新对象集（下图的 <code class="docutils literal notranslate"><span class="pre">object</span> <span class="pre">set</span> <span class="pre">2</span></code> ），
然后从新对象集中的第一个对象（下图中的 <code class="docutils literal notranslate"><span class="pre">object</span> <span class="pre">4</span></code> ）起开始写入第一个条带（ <code class="docutils literal notranslate"><span class="pre">stripe</span> <span class="pre">unit</span> <span class="pre">16</span></code> ）。</p>
<p>三个重要变量决定着 Ceph 如何条带化数据：</p>
<ul class="simple">
<li><p><strong>对象尺寸：</strong> Ceph 存储集群里的对象有最大可配置尺寸
（如 2MB 、 4MB 等等），对象尺寸必须足够大才能容纳很多条带单元、
而且应该是条带单元的整数倍。</p></li>
<li><p><strong>条带宽度：</strong> 条带都有可配置的单位尺寸（如 64KB ）。
Ceph 客户端把数据等分成适合写入对象的条带单元，
除了最后一个。条带宽度应该是对象尺寸的分数片段，
这样对象才能包含很多条带单元。</p></li>
<li><p><strong>条带数量：</strong> Ceph 客户端把一系列条带单元写入由条带数量所确定的一系列对象，
这一系列的对象称为一个对象集。
客户端写到对象集内的最后一个对象时，
再返回到第一个。</p></li>
</ul>
<div class="admonition important">
<p class="admonition-title">Important</p>
<p>把集群投入生产环境前要先测试条带化配置，因为把数据条带化到对象中之后这些参数就<strong>不可</strong>更改了。</p>
</div>
<p>Ceph 客户端把数据等分为条带单元并映射到对象后，
用 CRUSH 算法把对象映射到归置组、归置组映射到 OSD ，
然后才能以文件形式存储到硬盘上。</p>
<div class="admonition note">
<p class="admonition-title">Note</p>
<p>因为客户端写入单个存储池，条带为对象的所有数据也被映射到同一存储池内的归置组，所以它们要共享相同的 CRUSH 图和相同的访问权限。</p>
</div>
</div>
</div>
<div class="section" id="index-17">
<span id="id22"></span><h2>Ceph 客户端<a class="headerlink" href="#index-17" title="Permalink to this headline">¶</a></h2>
<p>Ceph 客户端包括数种服务接口，有：</p>
<ul class="simple">
<li><p><strong>块设备：</strong> <a class="reference internal" href="../glossary/#term-16"><span class="xref std std-term">Ceph 块设备</span></a>（也叫 RBD ）服务提供了大小可调、精炼、支持快照和克隆。为提供高性能，
Ceph 把块设备条带化到整个集群。
Ceph 同时支持直接使用 <code class="docutils literal notranslate"><span class="pre">librbd</span></code> 的内核对象（ KO ）和
QEMU 管理程序——避免了虚拟系统上的内核对象过载。</p></li>
<li><p><strong>对象存储：</strong> <a class="reference internal" href="../glossary/#term-13"><span class="xref std std-term">Ceph 对象存储</span></a>（也叫 RGW ）服务提供了 <a class="reference external" href="https://en.wikipedia.org/wiki/RESTful">REST 风格</a>的 API ，它有与 Amazon S3 和
OpenStack Swift 兼容的接口。</p></li>
<li><p><strong>文件系统：</strong> <a class="reference internal" href="../glossary/#term-18"><span class="xref std std-term">Ceph 文件系统</span></a>（ CephFS ）服务提供了兼容 POSIX 的文件系统，可以直接 <code class="docutils literal notranslate"><span class="pre">mount</span></code> 或挂载为用户空间文件系统（ FUSE ）。</p></li>
</ul>
<p>Ceph 能额外运行多个 OSD 、 MDS 、和监视器来保证伸缩性和高可靠性，下图描述了高级架构。</p>
<div class="section" id="index-18">
<span id="id23"></span><h3>Ceph 对象存储<a class="headerlink" href="#index-18" title="Permalink to this headline">¶</a></h3>
<p>Ceph 对象存储守护进程是 <code class="docutils literal notranslate"><span class="pre">radosgw</span></code> ，它是一个 FastCGI 服务，
提供了 <a class="reference external" href="https://en.wikipedia.org/wiki/RESTful">REST 风格</a> HTTP API 用于存储对象和元数据。
它坐落于 Ceph 存储集群之上，有自己的数据格式，
并维护着自己的用户数据库、认证、和访问控制。
RADOS 网关使用统一的命名空间，也就是说，
你可以用 OpenStack Swift 兼容的 API 或者 Amazon S3 兼容的 API ；
例如，你可以用一个程序通过 S3 兼容 API 写入数据、
然后用另一个程序通过 Swift 兼容 API 读出。</p>
<div class="topic">
<p class="topic-title">S3/Swift 对象和存储集群对象比较</p>
<p>Ceph 对象存储用<em>对象</em>这个术语来描述它存储的数据。
S3 和 Swift 对象不同于 Ceph 写入存储集群的对象，
Ceph 对象存储系统内的对象可以映射到 Ceph 存储集群内的对象；
S3 和 Swift 对象却不一定 1:1 地映射到存储集群内的对象，
它有可能映射到了多个 Ceph 对象。</p>
</div>
<p>详情见 <a class="reference external" href="../radosgw/">Ceph 对象存储</a>。</p>
</div>
<div class="section" id="index-19">
<span id="id24"></span><h3>Ceph 块设备<a class="headerlink" href="#index-19" title="Permalink to this headline">¶</a></h3>
<p>Ceph 块设备把一个设备映像条带化到集群内的多个对象，
其中各对象映射到一个归置组并分布出去，
这些归置组会散播到整个集群的某些 <code class="docutils literal notranslate"><span class="pre">ceph-osd</span></code> 守护进程。</p>
<div class="admonition important">
<p class="admonition-title">Important</p>
<p>条带化会使 RBD 块设备比单台服务器运行的更好！</p>
</div>
<p>瘦接口、可快照的 Ceph 块设备对虚拟化和云计算很有吸引力。
在虚拟机场景中，人们一般会用 QEMU/KVM 中的
<code class="docutils literal notranslate"><span class="pre">rbd</span></code> 网络存储驱动部署 Ceph 块设备，其中宿主机用
<code class="docutils literal notranslate"><span class="pre">librbd</span></code> 向访客提供块设备服务；很多云计算堆栈用
<code class="docutils literal notranslate"><span class="pre">libvirt</span></code> 和管理程序集成。你可以用简配的 Ceph 块设备搭配
QEMU 和``libvirt`` 来支持 OpenStack 和 CloudStack ，
一起构成完整的方案。</p>
<p>现在还没其它管理程序支持 <code class="docutils literal notranslate"><span class="pre">librbd</span></code> ，
你可以用 Ceph 块设备内核对象向客户端提供块设备。
其它虚拟化技术，像 Xen 能访问 Ceph 块设备内核对象，
用命令行工具 <code class="docutils literal notranslate"><span class="pre">rbd</span></code> 实现。</p>
</div>
<div class="section" id="arch-cephfs">
<span id="index-20"></span><span id="id25"></span><h3>Ceph 文件系统<a class="headerlink" href="#arch-cephfs" title="Permalink to this headline">¶</a></h3>
<p>Ceph 文件系统（ CephFS ）是与 POSIX 兼容的文件系统服务，坐落于基于对象的 Ceph 存储集群之上，
其内的文件被映射到 Ceph 存储集群内的对象。
客户端可以把此文件系统挂载为内核对象或用户空间文件系统（ FUSE ）。</p>
<p>Ceph 文件系统服务包含随 Ceph 存储集群部署的元数据服务器（ MDS ）。
MDS 的作用是把所有文件系统元数据
（目录、文件所有者、访问模式等等）
永久存储在相当可靠的元数据服务器中，
元数据驻留在内存中。 MDS （名为 <code class="docutils literal notranslate"><span class="pre">ceph-mds</span></code> 的守护进程）存在的原因是，
简单的文件系统操作像列出目录（ <code class="docutils literal notranslate"><span class="pre">ls</span></code> ）、
或进入目录（ <code class="docutils literal notranslate"><span class="pre">cd</span></code> ），这些操作本无需扰动 <code class="docutils literal notranslate"><span class="pre">OSD</span></code> 。
所以把元数据从数据里分出来意味着 Ceph 文件系统能提供高性能服务，
又没额外增加存储集群负载。</p>
<p>CephFS 从数据中分离出了元数据、并存储于 MDS ，
文件数据存储于存储集群中的一或多个对象。
Ceph 力争兼容 POSIX 。 <code class="docutils literal notranslate"><span class="pre">ceph-mds</span></code> 可以只运行一个，
也可以分布于多台物理机器，
以获得高可用性或伸缩性。</p>
<ul class="simple">
<li><p><strong>高可用性：</strong> 多余的 <code class="docutils literal notranslate"><span class="pre">ceph-mds</span></code> 例程可处于 <cite>standby</cite> （待命）状态，
随时准备替下之前处于 <cite>active</cite> （活跃）状态的失败 <code class="docutils literal notranslate"><span class="pre">ceph-mds</span></code> 。
这可以轻易做到，因为所有数据、包括日志都存储在 RADOS 上，
这个转换过程由 <code class="docutils literal notranslate"><span class="pre">ceph-mon</span></code> 自动触发。</p></li>
<li><p><strong>伸缩性：</strong> 多个 <code class="docutils literal notranslate"><span class="pre">ceph-mds</span></code> 例程可以同时处于 <cite>active</cite> 状态，
它们会把目录树拆分为子树（和单个热点目录的碎片），
在所有活跃服务器间高效地均衡负载。</p></li>
</ul>
<p>待命（ <cite>standby</cite> ）和活跃（ <cite>active</cite> ） MDS 可组合，例如，
运行 3 个处于 <cite>active</cite> 状态的 <code class="docutils literal notranslate"><span class="pre">ceph-mds</span></code> 例程以实现扩展、
和 1 个 <cite>standby</cite> 例程以实现高可用性。</p>
</div>
</div>
</div>



           </div>
           
          </div>
          <footer>
    <div class="rst-footer-buttons" role="navigation" aria-label="footer navigation">
        <a href="../dev/developer_guide/" class="btn btn-neutral float-right" title="向 Ceph 贡献：开发者指南" accesskey="n" rel="next">Next <span class="fa fa-arrow-circle-right" aria-hidden="true"></span></a>
        <a href="../api/mon_command_api/" class="btn btn-neutral float-left" title="cephadm" accesskey="p" rel="prev"><span class="fa fa-arrow-circle-left" aria-hidden="true"></span> Previous</a>
    </div>

  <hr/>

  <div role="contentinfo">
    <p>
        &#169; Copyright 2016, Ceph authors and contributors. Licensed under Creative Commons Attribution Share Alike 3.0 (CC-BY-SA-3.0).

    </p>
  </div> 

</footer>
        </div>
      </div>

    </section>

  </div>
  

  <script type="text/javascript">
      jQuery(function () {
          SphinxRtdTheme.Navigation.enable(true);
      });
  </script>

  
  
    
   

</body>
</html>